package com.mtons.mblog.base.model.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 缓存控制线程切面
 * @author yunhao.qi
 */
@Aspect
@Component
public class CacheThreadAspect {

    private static final Logger logger = LoggerFactory.getLogger(CacheThreadAspect.class);

    private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    private static final Integer MAX_THREAD_NUM = 10;//当前默认操作允许的最大线程数

    @Autowired
    private RedisTemplate redisTemplate;

    //定义切点1，用于控制线程数功能，配置需要切到的方法
    @Pointcut("@annotation(com.mtons.mblog.base.model.annotation.CacheThread)")
    public void cacheThreadController() {}

    //定义切点2，参数传入key，控制线程数
    @Pointcut("@annotation(com.mtons.mblog.base.model.annotation.CacheThreadArg)")
    public void cacheThreadArgKey(){}


    //定义前置通知
    @Before(value = "cacheThreadController()")
    public void openCacheThreadNum(JoinPoint joinPoint) {

        //1.获取注解
        Object [] param = joinPoint.getArgs();//获得切面方法当中的参数
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();//获得切面当中方法签名
        Method method = methodSignature.getMethod();//获得签名方法
        CacheThread cacheThread = method.getAnnotation(CacheThread.class);

        //2.获取注解参数
        String uniqueKey = cacheThread.prefix() + cacheThread.key();
        String number = cacheThread.number();
        String reminder = cacheThread.reminder();
        long timeout = cacheThread.timeout();
        TimeUnit timeUnit = cacheThread.timeUnit();
        logger.info(uniqueKey + " openCacheThreadNum ···");

        //3.添加缓存
        setThreadCache(uniqueKey, number, reminder, timeUnit,timeout);
    }

    @After(value = "cacheThreadController()")
    public void closeCacheThreadNum(JoinPoint joinPoint) {

        //1.获取注解
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();//获得切面当中方法签名
        Method method = methodSignature.getMethod();//获得签名方法
        CacheThread cacheThread = method.getAnnotation(CacheThread.class);//获得在方法上面的注解

        //2.获取操作key对应的set集合
        String uniqueKey = cacheThread.prefix() + cacheThread.key();
        logger.info(uniqueKey + " closeCacheThreadNum ···");

        //3.删除缓存
        removeThreadCache(uniqueKey);
    }


    @Before(value = "cacheThreadArgKey()")
    public void openCacheThreadByArgKey(JoinPoint joinPoint) {

        //1.获取注解
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();//获得切面当中方法签名
        Method method = methodSignature.getMethod();//获得签名方法
        CacheThreadArg cacheThreadArg = method.getAnnotation(CacheThreadArg.class);//获得在方法上面的注解

        //2.获取注解参数
        String argUniqueKey = cacheThreadArg.prefix() + parseKey(cacheThreadArg.argKey(), method, joinPoint.getArgs());
        String number = cacheThreadArg.number();
        String reminder = cacheThreadArg.reminder();
        long timeout = cacheThreadArg.timeout();
        TimeUnit timeUnit = cacheThreadArg.timeUnit();
        logger.info(argUniqueKey + " openCacheThreadByArgKey ···");

        setThreadCache(argUniqueKey, number, reminder, timeUnit,timeout);
    }

    @After(value = "cacheThreadArgKey()")
    public void closeCacheThreadByArgKey(JoinPoint joinPoint) {

        //1.获取注解
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();//获得切面当中方法签名
        Method method = methodSignature.getMethod();//获得签名方法
        CacheThreadArg cacheThreadArg = method.getAnnotation(CacheThreadArg.class);//获得在方法上面的注解

        //2.获取注解参数
        String argUniqueKey = cacheThreadArg.prefix() + parseKey(cacheThreadArg.argKey(), method, joinPoint.getArgs());
        logger.info(argUniqueKey + " closeCacheThreadByArgKey ···");

        //3.删除缓存
        removeThreadCache(argUniqueKey);
    }

    /**
     * 添加缓存
     * @param key
     * @param number
     * @param reminder
     */
    private void setThreadCache(String key, String number, String reminder, TimeUnit timeUnit, long timeout) {

        //1.获取操作key对应的set集合
        ValueOperations valueOperations = redisTemplate.opsForValue();
        Set threadIdSet = (Set) valueOperations.get(key);
        if (threadIdSet == null) {
            threadIdSet = new HashSet();
        }

        //2.最大线程数检验
        if(number != null && !"".equals(number)) {
            //传入线程数
            if (threadIdSet.size() >= Long.valueOf(number)) {
                logger.warn("operateKey: " + key + "has " + threadIdSet.size() + " thread , max thread number is " + number);
                if(reminder == null || "".equals(reminder)) {
                    throw new RuntimeException("当前相同操作进行中，请稍后再试！");
                } else {
                    throw new RuntimeException(reminder + "，请稍后再试！");
                }
            }
        } else {
            //使用默认的最大线程数
            if (threadIdSet.size() >= MAX_THREAD_NUM) {
                logger.warn("operateKey: " + key + "has " + threadIdSet.size() + " thread , max thread number is " + MAX_THREAD_NUM);
                throw new RuntimeException("当前操作最大线程数为：" + MAX_THREAD_NUM + "，请稍后再试");
            }
        }

        //3.重复线程数检验
        long threadId = Thread.currentThread().getId();
        logger.info("---->当前线程id：" + threadId);
        if (threadIdSet.contains(threadId)) {
            logger.warn("operateKey: " + key + ", threadId: " + threadId + "has been already exist in thread set !");
            throw new RuntimeException("当前线程已在" + key + "中，请稍后再试！");
        }

        //4.将新添加元素的set放如对应的操作key中
        threadIdSet.add(threadId);

        if(timeUnit !=null && timeout > 0L) {
            valueOperations.set(key, threadIdSet,timeout,timeUnit);
        } else {
            valueOperations.set(key, threadIdSet);
        }

    }

    /**
     * 删除缓存
     * @param key
     */
    private void removeThreadCache(String key) {

        ValueOperations valueOperations = redisTemplate.opsForValue();
        Set threadIdSet = (Set) valueOperations.get(key);
        if (threadIdSet == null || threadIdSet.isEmpty()) {
            logger.warn("operateKey: " + key + "，对应的set集合不存在");
            return;
        }

        //3.获取当前线程id
        long threadId = Thread.currentThread().getId();
        logger.info("---->当前线程id：" + threadId);
        if (!threadIdSet.contains(threadId)) {
            logger.warn("operateKey: " + key + ", threadId: " + threadId + "has not exist in thread set !");
            return;
        }

        //4.从对应的set集合中删除此线程id
        threadIdSet.remove(threadId);
        valueOperations.set(key, threadIdSet);
    }




    /**
     * key 定义在注解上，支持SPEL表达式
     * @param keyArr
     * @param method
     * @param args
     * @return
     */
    private String parseKey(String[] keyArr,Method method,Object [] args){

        if(keyArr == null || keyArr.length == 0) {
            logger.error("keyArr is null or length is 0");
            throw new RuntimeException("keyArr is null or length is 0");
        }

        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String [] paraNameArr=u.getParameterNames(method);

        //使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        //把方法参数放入SPEL上下文中
        for(int i=0;i<paraNameArr.length;i++){
            context.setVariable(paraNameArr[i], args[i]);
        }

        String uniqueKey = "";
        if(keyArr != null && keyArr.length>0){
            for(int i = 0; i < keyArr.length; i ++) {
                uniqueKey = uniqueKey + parser.parseExpression(keyArr[i]).getValue(context,String.class);
            }
        }

        return "unitKey:"+uniqueKey;
    }


    /**
     * 使用SpeL解析缓存key
     *
     * @param method
     * @param args
     * @param expressionString
     * @return
     */
    private String parseCacheKey(Method method, Object[] args, String expressionString) {
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
        EvaluationContext context = new StandardEvaluationContext();
        if (parameterNames != null && parameterNames.length > 0
                && args != null && args.length > 0
                && args.length == parameterNames.length) {
            for (int i = 0, length = parameterNames.length; i < length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
        }
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = null;
        if(expressionString != null && !"".equals(expressionString)){
            expression = parser.parseExpression(expressionString);
            return String.valueOf(expression.getValue(context));
        }else{
            return null;
        }
    }

}
